feat: Phantom UI vocabulary, base template, and name customization#54
feat: Phantom UI vocabulary, base template, and name customization#54
Conversation
Promote the Project 2 design system end to end. Replace the 468-line
public/_base.html with the 1086-line warm cream / warm deep dark token
system, the 89-class phantom-* vocabulary, the dual-theme ECharts
phantom theme, and the window.phantomChart helper. Add the living style
guide at public/_components.html and the new landing at public/index.html.
Rewrite src/ui/login-page.ts to emit the new login surface using the
phantom-* tokens with Instrument Serif display, dropping 136 lines of
hand-crafted SVG.
Build ten reference example pages under public/_examples/ that the live
agent can read at runtime when it wants to remember how a dashboard, a
session table, an evolution timeline, a memory explorer, a skills editor,
a cost report, or a chat surface composes from the vocabulary. Each page
is self-contained so the agent does not need to also load _base.html.
Validated in both phantom-light and phantom-dark with zero console errors
and zero failed network requests.
Plumb the PHANTOM_NAME env var through every user-facing surface. Add
src/ui/name.ts with capitalizeAgentName and agentNameInitial helpers,
plus 13 unit tests. Add three new placeholder substitutions to
wrapInBaseTemplate ({{AGENT_NAME}}, {{AGENT_NAME_CAPITALIZED}},
{{AGENT_NAME_INITIAL}}) and thread the agent name through
createWebUiToolServer from src/index.ts. Wire setLoginPageAgentName
through the same call site so the login HTML interpolates the agent
name without modifying serve.ts. For the static landing, components,
and example pages, ship a small inline /health-fetch script that
populates data-agent-name markers at runtime with localStorage caching.
Replace the legacy 88-line UI guidance block in
src/agent/prompt-assembler.ts with the verbatim 155-line block from the
research implementation plan, extracted into
src/agent/prompt-blocks/ui-guidance.ts. Also extract the security and
instructions blocks so prompt-assembler.ts stays under 300 lines.
Tests: 948 before, 973 after (+25). Lint and typecheck clean.
Playwright validation captured for all ten example pages plus the style
guide and a wrapped base template page in scratch/02-project2/builder-
validation/. Measured baseline shows FCP improving from 244ms to 40ms,
DOM nodes 50 to 46, HTML bytes 27911 to 44347, with two new Instrument
Serif font requests and one expected SSE 404 on the local Python server.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 880589c175
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
scripts/install-phantom-ui-skill.sh
Outdated
| # discovers it once Project 3 wires settingSources to include 'user'. | ||
| set -euo pipefail | ||
|
|
||
| SOURCE_FILE="local/2026-04-12-phantom-ui-chapter/scratch/02-project2/phantom-ui-skill.md" |
There was a problem hiding this comment.
Point installer at a committed skill source file
The installer script hardcodes SOURCE_FILE to local/2026-04-12-phantom-ui-chapter/scratch/02-project2/phantom-ui-skill.md, but that file is not present in this commit, so the script always exits on the existence check and never installs anything. This makes the new install workflow unusable out of the box; update the source path to a tracked file (or commit the referenced seed file).
Useful? React with 👍 / 👎.
src/ui/login-page.ts
Outdated
| <title>Sign in - Phantom</title> | ||
|
|
||
| <!-- Flash prevention --> | ||
| <title>Sign in - ${displayName}</title> |
There was a problem hiding this comment.
Escape agent name before injecting into login markup
displayName is interpolated directly into the login HTML template without entity escaping. Since config.name is only validated as a non-empty string, a value containing markup (for example from YAML or PHANTOM_NAME) can inject HTML/JS into /ui/login and also break rendering for names with special characters. Escape once before template interpolation and use the escaped value everywhere in the HTML string.
Useful? React with 👍 / 👎.
- fix wrapInBaseTemplate dollar-pattern content corruption via single-pass regex
- escape displayName in login-page.ts to harden PHANTOM_NAME against XSS
- complete escapeHtml with apostrophe and extract to shared src/ui/html.ts
- replace transition: all with explicit property lists in style guide and examples
- fix duplicate class attribute on navbar date span, drive visibility via CSS
- swap hardcoded #fff in _components.html for --color-error-content token
- swap hardcoded chart hex colors for theme token reads via getComputedStyle
- deduplicate the /health name-fetch IIFE into public/_agent-name.js
- point skill installer at tracked scripts/skills/phantom-ui.md seed
- add wrapInBaseTemplate edge-case tests for dollar patterns and HTML-special names
- drop brittle toContain("Phantom") assertion from the login-page serve test
- extract buildEvolvedSections and buildWorkingMemory from prompt-assembler
Summary
Ships the warm cream / warm deep dark
phantom-*design system end to end. This is the foundation downstream UI work builds on top of.public/_base.html(468 lines) with a 1086-line vocabulary template, a dual-theme ECharts phantom theme, and thewindow.phantomCharthelper. Addspublic/_components.htmlas a living style guide the live agent reads at runtime.public/index.htmland rewritessrc/ui/login-page.ts(305 to 169 lines) to emit a new login surface usingphantom-*tokens with Instrument Serif display.public/_examples/(01-landing through 10-chat-active). Each is a self-contained composition the live agent reads when authoring a new surface.PHANTOM_NAMEthrough every user-facing surface.wrapInBaseTemplatesubstitutes{{AGENT_NAME}},{{AGENT_NAME_CAPITALIZED}},{{AGENT_NAME_INITIAL}}for agent-authored pages. The static landing, components, and example pages get a small inline/health-fetch script that populatesdata-agent-namemarkers at runtime with localStorage caching.src/agent/prompt-assembler.tswith a richer block extracted tosrc/agent/prompt-blocks/ui-guidance.ts. Also extracts security and instructions blocks soprompt-assembler.tsstays under 300 lines (426 to 296).Measured baseline (old vs new template)
_base.html_base.htmlBigger HTML, fewer DOM nodes, faster first paint. Two new Instrument Serif font files for the new display face.
Test plan
bun test+25 tests, 0 failures, 0 regressionsbun run lintclean (Biome, 211 files)bun run typecheckclean (tsc --noEmit)_components.htmlplus a wrapped_base.htmltest page in bothphantom-lightandphantom-darkthemes, zero console errors, zero failed network requestspublic/_base.html,public/_components.html,public/index.html, andpublic/_examples/*.html. Only allowed matches are thephantom-*CSS prefix and internal code commentsprompt-assembler.tsslimmed from 426 to 296 via the split intoprompt-blocks/docker-compose.yaml,Dockerfile,src/ui/serve.ts,src/core/graceful.ts,src/agent/runtime.ts,src/core/server.ts,phantom-config/, orconfig/Rollback
Single-path deploy, no feature flag. Rollback via
git revertof the feature commit if needed.